package com.zyb.beidada.controller;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zhipu.oapi.service.v4.model.ModelData;
import com.zyb.beidada.annotation.AuthCheck;
import com.zyb.beidada.common.BaseResponse;
import com.zyb.beidada.common.DeleteRequest;
import com.zyb.beidada.common.ErrorCode;
import com.zyb.beidada.common.ResultUtils;
import com.zyb.beidada.constant.UserConstant;
import com.zyb.beidada.exception.BusinessException;
import com.zyb.beidada.exception.ThrowUtils;
import com.zyb.beidada.manager.AIManager;
import com.zyb.beidada.model.dto.question.*;
import com.zyb.beidada.model.entity.App;
import com.zyb.beidada.model.entity.Question;
import com.zyb.beidada.model.entity.User;
import com.zyb.beidada.model.enums.AppTypeEnum;
import com.zyb.beidada.model.enums.UserRoleEnum;
import com.zyb.beidada.model.vo.QuestionVO;
import com.zyb.beidada.service.AppService;
import com.zyb.beidada.service.QuestionService;
import com.zyb.beidada.service.UserService;
import io.reactivex.Flowable;
import io.reactivex.Scheduler;
import io.reactivex.schedulers.Schedulers;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 问卷接口
 *
 * @author <a href="https://github.com/liyupi">程序员鱼皮</a>
 * @from <a href="https://www.code-nav.cn">编程导航学习圈</a>
 */
@RestController
@RequestMapping("/question")
@Slf4j
public class QuestionController {

    @Resource
    private QuestionService questionService;

    @Resource
    private UserService userService;

    @Resource
    private AppService appService;

    @Resource
    private AIManager aiManager;

    @Resource
    private Scheduler vipScheduler;

    // region 增删改查

    /**
     * 创建问卷
     *
     * @param questionAddRequest
     * @param request
     * @return
     */
    @PostMapping("/add")
    public BaseResponse<Long> addQuestion(@RequestBody QuestionAddRequest questionAddRequest, HttpServletRequest request) {
        ThrowUtils.throwIf(questionAddRequest == null, ErrorCode.PARAMS_ERROR);
        // todo 在此处将实体类和 DTO 进行转换
        Question question = new Question();
        BeanUtils.copyProperties(questionAddRequest, question);
        // 数据校验
        questionService.validQuestion(question, true);
        // todo 填充默认值
        User loginUser = userService.getLoginUser(request);
        question.setUserId(loginUser.getId());
        // 写入数据库
        boolean result = questionService.save(question);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        // 返回新写入的数据 id
        long newQuestionId = question.getId();
        return ResultUtils.success(newQuestionId);
    }

    /**
     * 删除问卷
     *
     * @param deleteRequest
     * @param request
     * @return
     */
    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteQuestion(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
        if (deleteRequest == null || deleteRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User user = userService.getLoginUser(request);
        long id = deleteRequest.getId();
        // 判断是否存在
        Question oldQuestion = questionService.getById(id);
        ThrowUtils.throwIf(oldQuestion == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可删除
        if (!oldQuestion.getUserId().equals(user.getId()) && !userService.isAdmin(request)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        // 操作数据库
        boolean result = questionService.removeById(id);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return ResultUtils.success(true);
    }

    /**
     * 更新问卷（仅管理员可用）
     *
     * @param questionUpdateRequest
     * @return
     */
    @PostMapping("/update")
    @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
    public BaseResponse<Boolean> updateQuestion(@RequestBody QuestionUpdateRequest questionUpdateRequest) {
        if (questionUpdateRequest == null || questionUpdateRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // todo 在此处将实体类和 DTO 进行转换
        Question question = new Question();
        BeanUtils.copyProperties(questionUpdateRequest, question);
        // 数据校验
        questionService.validQuestion(question, false);
        // 判断是否存在
        long id = questionUpdateRequest.getId();
        Question oldQuestion = questionService.getById(id);
        ThrowUtils.throwIf(oldQuestion == null, ErrorCode.NOT_FOUND_ERROR);
        // 操作数据库
        boolean result = questionService.updateById(question);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return ResultUtils.success(true);
    }

    /**
     * 根据 id 获取问卷（封装类）
     *
     * @param id
     * @return
     */
    @GetMapping("/get/vo")
    public BaseResponse<QuestionVO> getQuestionVOById(long id, HttpServletRequest request) {
        ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Question question = questionService.getById(id);
        ThrowUtils.throwIf(question == null, ErrorCode.NOT_FOUND_ERROR);
        // 获取封装类
        return ResultUtils.success(questionService.getQuestionVO(question, request));
    }

    /**
     * 分页获取问卷列表（仅管理员可用）
     *
     * @param questionQueryRequest
     * @return
     */
    @PostMapping("/list/page")
    @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
    public BaseResponse<Page<Question>> listQuestionByPage(@RequestBody QuestionQueryRequest questionQueryRequest) {
        long current = questionQueryRequest.getCurrent();
        long size = questionQueryRequest.getPageSize();
        // 查询数据库
        Page<Question> questionPage = questionService.page(new Page<>(current, size),
                questionService.getQueryWrapper(questionQueryRequest));
        return ResultUtils.success(questionPage);
    }

    /**
     * 分页获取问卷列表（封装类）
     *
     * @param questionQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/list/page/vo")
    public BaseResponse<Page<QuestionVO>> listQuestionVOByPage(@RequestBody QuestionQueryRequest questionQueryRequest,
                                                               HttpServletRequest request) {
        long current = questionQueryRequest.getCurrent();
        long size = questionQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Page<Question> questionPage = questionService.page(new Page<>(current, size),
                questionService.getQueryWrapper(questionQueryRequest));
        // 获取封装类
        return ResultUtils.success(questionService.getQuestionVOPage(questionPage, request));
    }

    /**
     * 分页获取当前登录用户创建的问卷列表
     *
     * @param questionQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/my/list/page/vo")
    public BaseResponse<Page<QuestionVO>> listMyQuestionVOByPage(@RequestBody QuestionQueryRequest questionQueryRequest,
                                                                 HttpServletRequest request) {
        ThrowUtils.throwIf(questionQueryRequest == null, ErrorCode.PARAMS_ERROR);
        // 补充查询条件，只查询当前登录用户的数据
        User loginUser = userService.getLoginUser(request);
        questionQueryRequest.setUserId(loginUser.getId());
        long current = questionQueryRequest.getCurrent();
        long size = questionQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Page<Question> questionPage = questionService.page(new Page<>(current, size),
                questionService.getQueryWrapper(questionQueryRequest));
        // 获取封装类
        return ResultUtils.success(questionService.getQuestionVOPage(questionPage, request));
    }

    /**
     * 编辑问卷（给用户使用）
     *
     * @param questionEditRequest
     * @param request
     * @return
     */
    @PostMapping("/edit")
    public BaseResponse<Boolean> editQuestion(@RequestBody QuestionEditRequest questionEditRequest, HttpServletRequest request) {
        if (questionEditRequest == null || questionEditRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // todo 在此处将实体类和 DTO 进行转换
        Question question = new Question();
        BeanUtils.copyProperties(questionEditRequest, question);
        // 数据校验
        questionService.validQuestion(question, false);
        User loginUser = userService.getLoginUser(request);
        // 判断是否存在
        long id = questionEditRequest.getId();
        Question oldQuestion = questionService.getById(id);
        ThrowUtils.throwIf(oldQuestion == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可编辑
        if (!oldQuestion.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        // 操作数据库
        boolean result = questionService.updateById(question);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return ResultUtils.success(true);
    }

    // endregion

    //system prompt
    private static final String GENERATE_QUESTION_SYSTEM_MESSAGE = "你是一位严谨的出题专家，我会给你如下信息：\n" +
            "```\n" +
            "应用名称，\n" +
            "【【【应用描述】】】，\n" +
            "应用类别，\n" +
            "要生成的题目数，\n" +
            "每个题目的选项数\n" +
            "```\n" +
            "\n" +
            "请你根据上述信息，按照以下步骤来出题：\n" +
            "1. 要求：题目和选项尽可能地短，题目不要包含序号，每题的选项数以我提供的为主，题目不能重复\n" +
            "2. 严格按照下面的 json 格式输出题目和选项\n" +
            "```\n" +
            "[{\"options\":[{\"value\":\"选项内容\",\"key\":\"A\"},{\"value\":\"\",\"key\":\"B\"}],\"title\":\"题目标题\"}]\n" +
            "```\n" +
            "title 是题目，options 是选项，每个选项的 key 按照英文字母序（比如 A、B、C、D）以此类推，value 是选项内容\n" +
            "3. 检查题目是否包含序号，若包含序号则去除序号\n" +
            "4. 返回的题目列表格式必须为 JSON 数组";

    /**
     * 拼接用户prompt
     *
     * @param app
     * @param questionNumber
     * @param optionNumber
     * @return
     */
    private String getGenerateQuestionUserMessage(App app, int questionNumber, int optionNumber) {
        StringBuilder userMessage = new StringBuilder();
        userMessage.append(app.getAppName()).append("\n");
        userMessage.append(app.getAppDesc()).append("\n");
        userMessage.append(AppTypeEnum.fromValue(app.getAppType()).getText()).append("\n");
        userMessage.append(questionNumber).append("\n");
        userMessage.append(optionNumber);
        return userMessage.toString();
    }


    /**
     * 使用AI生成题目
     *
     * @param aiGenerateRequest
     * @param request
     * @return
     */
    @PostMapping("/ai_generate")
    public BaseResponse<List<QuestionContentDTO>> generateQuestionByAI(@RequestBody AIGenerateRequest aiGenerateRequest, HttpServletRequest request) {
        //请求参数校验
        ThrowUtils.throwIf(aiGenerateRequest == null, ErrorCode.PARAMS_ERROR, "请求参数不能为空");
        //根据appid拿到必须参数
        Long appId = aiGenerateRequest.getAppId();
        App app = appService.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.PARAMS_ERROR, "该app不存在");
        //拼接参数调用ai生成题目
        String generateQuestionUserMessage = getGenerateQuestionUserMessage(app, aiGenerateRequest.getQuestionNum(), aiGenerateRequest.getOptionNum());
        String resultJson = aiManager.solveWithChatGLM4(GENERATE_QUESTION_SYSTEM_MESSAGE, generateQuestionUserMessage);
        //截取结果转化成对象返回
        int start = resultJson.indexOf("[");
        int end = resultJson.lastIndexOf("]");
        List<QuestionContentDTO> result = JSONUtil.toList(resultJson.substring(start, end + 1), QuestionContentDTO.class);
        return ResultUtils.success(result);
    }


    /**
     * 使用AI生成题目,流式获取
     *
     * @param aiGenerateRequest
     * @param request
     * @return
     */
    @GetMapping("/ai_generate/sse")
    public SseEmitter generateQuestionByAIWithStream(AIGenerateRequest aiGenerateRequest, HttpServletRequest request) {
        //请求参数校验
        ThrowUtils.throwIf(aiGenerateRequest == null, ErrorCode.PARAMS_ERROR, "请求参数不能为空");
        //根据appid拿到必须参数
        Long appId = aiGenerateRequest.getAppId();
        App app = appService.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.PARAMS_ERROR, "该app不存在");
        //拼接参数调用ai流式生成题目
        String generateQuestionUserMessage = getGenerateQuestionUserMessage(app, aiGenerateRequest.getQuestionNum(), aiGenerateRequest.getOptionNum());
        Flowable<ModelData> resultFlowable = aiManager.solveWithChatGLM4ByStream(GENERATE_QUESTION_SYSTEM_MESSAGE, generateQuestionUserMessage);
        //创建SseEmitter
        SseEmitter emitter = new SseEmitter(0L);
        //数据流无需做任何处理，得到之后的拼接即可
        StringBuilder resultJoiner = new StringBuilder();
        //检索游标
        AtomicReference<Integer> flag = new AtomicReference<>(0);
        //根据用户身份选择线程池
//        User loginUser = userService.getLoginUser(request);
//        String userRole = loginUser.getUserRole();
        Scheduler scheduler = Schedulers.io();
//        if (StrUtil.isNotBlank(userRole) && UserRoleEnum.ADMIN.getValue().equals(userRole)) {
//            scheduler = vipScheduler;
//        }
        resultFlowable
                // 异步线程池执行
                .observeOn(scheduler)
                .doOnNext(
                        item -> {
                            String content = item.getChoices().get(0).getDelta().getContent();
//                            log.info("此次接收的数据为：" + content);
                            resultJoiner.append(content);
//                            log.info("此时数据集合：" + resultJoiner);
                            //使用算法处理字符串，如果组成完成信息则通过连接器返回事件并携带一条完整的数据
                            String data = getOneFullDataInStringBuilder(resultJoiner, flag.get());
                            if (data != null) {
                                log.info("收集到完整的的数据为：" + data);
                                emitter.send(JSONUtil.toJsonStr(data));
                                //只有在成功的返回了完整数据的时候才能移动游标
                                flag.set(resultJoiner.length());
                            }
                        }
                )
                .doOnComplete(emitter::complete)
                .subscribe();
        return emitter;
    }

    /**
     * 使用算法处理字符串，如果组成完整一条数据则返回，否则返回null
     * @param flag 记号,下次遍历的位置
     * @param resultJoiner
     * @return
     */
    private String getOneFullDataInStringBuilder(StringBuilder resultJoiner,Integer flag) {
        //创建符号栈
        Deque<Map.Entry<String, Integer>> stack = new ArrayDeque<>();
        //创建符合进栈的符号set
        HashSet<String> symbols = new HashSet<>(Arrays.asList("[", "{"));
        //遍历字符串
        for (int i = flag; i < resultJoiner.length(); i++) {
            String c = resultJoiner.substring(i, i + 1);

            if (c == null) continue;

            //如果符号栈不为空，则判断当前符号是否为进栈符号
            if (symbols.contains(c)) {
                stack.push(new AbstractMap.SimpleEntry<>(c, i));
                continue;
            }
            //如果符号栈不为空，且当前符号 } ,可以与栈顶相消
            if (stack.peek() != null && c.equals("}") && stack.peek().getKey().equals("{")) {
                //匹配成功，则将栈顶符号出栈
                Map.Entry<String, Integer> start = stack.pop();
                //判断栈长度是否为1，且只有【，如果是则说明已经组成完整一条数据
                if ((stack.size() == 1 && stack.peek().getKey().equals("[")) || stack.isEmpty()) {
                    return resultJoiner.substring(start.getValue(), i + 1);
                }
            } else if (stack.peek() != null && c.equals("]") && stack.peek().getKey().equals("[")) {
                //匹配成功，则将栈顶符号出栈
                stack.pop();
            }
        }

        return null;
    }
}
